using System;
using System.Collections.Generic;
using Xunit;

namespace CodePlex.TfsLibrary
{
    public class UniversalDiffTest
    {
        static void AssertBlockMetadata(UniversalDiff.Block block,
                                        int leftStart,
                                        int leftLength,
                                        int rightStart,
                                        int rightLength)
        {
            Assert.Equal(leftStart, block.LeftStartLine);
            Assert.Equal(leftLength, block.LeftContentLength);
            Assert.Equal(rightStart, block.RightStartLine);
            Assert.Equal(rightLength, block.RightContentLength);
        }

        static void AssertCorrectChunk(DiffEngine.Chunk chunk,
                                       DiffEngine.ChunkType type,
                                       params string[] lines)
        {
            Assert.Equal(type, chunk.ChunkType);
            Assert.Equal(lines.Length, chunk.LineCount);

            for (int idx = 0; idx < lines.Length; idx++)
                Assert.Equal(lines[idx], chunk.Lines[idx]);
        }

        [Fact]
        public void DoubleLineAdd()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(new string[0], new string[] { "a", "b" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 1, 0, 1, 2);
            Assert.Equal(1, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Right, "a", "b");
        }

        [Fact]
        public void DoubleLineDelete()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(new string[] { "a", "b" }, new string[0]);

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 1, 2, 1, 0);
            Assert.Equal(1, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Left, "a", "b");
        }

        [Fact]
        public void EmptyChunkList()
        {
            List<DiffEngine.Chunk> diff = new List<DiffEngine.Chunk>();

            UniversalDiff.Diff(diff, delegate
                                     {
                                         throw new InvalidOperationException("Should not get here");
                                     });
        }

        [Fact]
        public void EndsWithAdd()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(
                new string[] { "a", "b", "c", "d" },
                new string[] { "a", "b", "c", "d", "e", "f", "g", "h" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 2, 3, 2, 7);
            Assert.Equal(2, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Same, "b", "c", "d");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Right, "e", "f", "g", "h");
        }

        [Fact]
        public void EndsWithDelete()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(
                new string[] { "a", "b", "c", "d", "e", "f", "g", "h" },
                new string[] { "a", "b", "c", "d" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 2, 7, 2, 3);
            Assert.Equal(2, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Same, "b", "c", "d");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Left, "e", "f", "g", "h");
        }

        [Fact]
        public void EndWithSameBlockLengthGreaterThan3()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(
                new string[] { "e", "f", "g", "h" },
                new string[] { "a", "b", "c", "d", "e", "f", "g", "h" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 1, 3, 1, 7);
            Assert.Equal(2, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Right, "a", "b", "c", "d");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Same, "e", "f", "g");
        }

        [Fact]
        public void EndWithSameBlockLengthLessThan3()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(
                new string[] { "e", "f" },
                new string[] { "a", "b", "c", "d", "e", "f" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 1, 2, 1, 6);
            Assert.Equal(2, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Right, "a", "b", "c", "d");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Same, "e", "f");
        }

        [Fact]
        public void MultipleChanges()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(
                @"1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16",
                @"a
1
2
3
4
5
6
7
b
9
c
10
11
12
13
14
15
16
d");

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(3, blocks.Count);

            AssertBlockMetadata(blocks[0], 1, 3, 1, 4);
            Assert.Equal(2, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Right, "a");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Same, "1", "2", "3");

            AssertBlockMetadata(blocks[1], 5, 8, 6, 9);
            Assert.Equal(6, blocks[1].Chunks.Count);
            AssertCorrectChunk(blocks[1].Chunks[0], DiffEngine.ChunkType.Same, "5", "6", "7");
            AssertCorrectChunk(blocks[1].Chunks[1], DiffEngine.ChunkType.Left, "8");
            AssertCorrectChunk(blocks[1].Chunks[2], DiffEngine.ChunkType.Right, "b");
            AssertCorrectChunk(blocks[1].Chunks[3], DiffEngine.ChunkType.Same, "9");
            AssertCorrectChunk(blocks[1].Chunks[4], DiffEngine.ChunkType.Right, "c");
            AssertCorrectChunk(blocks[1].Chunks[5], DiffEngine.ChunkType.Same, "10", "11", "12");

            AssertBlockMetadata(blocks[2], 14, 3, 16, 4);
            Assert.Equal(2, blocks[2].Chunks.Count);
            AssertCorrectChunk(blocks[2].Chunks[0], DiffEngine.ChunkType.Same, "14", "15", "16");
            AssertCorrectChunk(blocks[2].Chunks[1], DiffEngine.ChunkType.Right, "d");
        }

        [Fact]
        public void NullDelegateThrows()
        {
            List<DiffEngine.Chunk> diff = new List<DiffEngine.Chunk>();

            Assert.Throws<ArgumentNullException>(delegate
                                                 {
                                                     UniversalDiff.Diff(diff, null);
                                                 });
        }

        [Fact]
        public void NullDiffDataThrows()
        {
            Assert.Throws<ArgumentNullException>(delegate
                                                 {
                                                     UniversalDiff.Diff(null, delegate {});
                                                 });
        }

        [Fact]
        public void SingleLineAdd()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(new string[0], new string[] { "a" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 1, 0, 1, 1);
            Assert.Equal(1, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Right, "a");
        }

        [Fact]
        public void SingleLineDelete()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(new string[] { "a" }, new string[0]);

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 1, 1, 1, 0);
            Assert.Equal(1, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Left, "a");
        }

        [Fact]
        public void SingleSameBlock()
        {
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff("a", "a");

            UniversalDiff.Diff(diff, delegate
                                     {
                                         throw new InvalidOperationException("Should not get here");
                                     });
        }

        [Fact]
        public void StartsWithAdd()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(new string[] { "a", "b", "c", "d" }, new string[] { "!", "@", "a", "b", "c", "d" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 1, 3, 1, 5);
            Assert.Equal(2, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Right, "!", "@");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Same, "a", "b", "c");
        }

        [Fact]
        public void StartsWithDelete()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(new string[] { "a", "b", "c", "d", "e", "f" }, new string[] { "e", "f" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 1, 6, 1, 2);
            Assert.Equal(2, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Left, "a", "b", "c", "d");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Same, "e", "f");
        }

        [Fact]
        public void StartsWithSameBlockGreaterThan3InLength()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(new string[] { "a", "b", "c", "d" }, new string[] { "a", "b", "c", "d", "e", "f" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 2, 3, 2, 5);
            Assert.Equal(2, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Same, "b", "c", "d");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Right, "e", "f");
        }

        [Fact]
        public void StartsWithSameBlockLessThan3InLength()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(new string[] { "a", "b" }, new string[] { "a", "b", "c", "d" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 1, 2, 1, 4);
            Assert.Equal(2, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Same, "a", "b");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Right, "c", "d");
        }

        [Fact]
        public void TwoChangesSeparatedBy6Lines()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(
                new string[] { "a", "1", "2", "3", "4", "5", "6", "d" },
                new string[] { "a", "b", "1", "2", "3", "4", "5", "6", "c", "d" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(1, blocks.Count);
            AssertBlockMetadata(blocks[0], 1, 8, 1, 10);
            Assert.Equal(5, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Same, "a");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Right, "b");
            AssertCorrectChunk(blocks[0].Chunks[2], DiffEngine.ChunkType.Same, "1", "2", "3", "4", "5", "6");
            AssertCorrectChunk(blocks[0].Chunks[3], DiffEngine.ChunkType.Right, "c");
            AssertCorrectChunk(blocks[0].Chunks[4], DiffEngine.ChunkType.Same, "d");
        }

        [Fact]
        public void TwoChangesSeparatedBy7Lines()
        {
            List<UniversalDiff.Block> blocks = new List<UniversalDiff.Block>();
            List<DiffEngine.Chunk> diff = DiffEngine.GetDiff(
                new string[] { "a", "1", "2", "3", "4", "5", "6", "7", "d" },
                new string[] { "a", "b", "1", "2", "3", "4", "5", "6", "7", "c", "d" });

            UniversalDiff.Diff(diff, delegate(UniversalDiff.Block block)
                                     {
                                         blocks.Add(block);
                                     });

            Assert.Equal(2, blocks.Count);

            AssertBlockMetadata(blocks[0], 1, 4, 1, 5);
            Assert.Equal(3, blocks[0].Chunks.Count);
            AssertCorrectChunk(blocks[0].Chunks[0], DiffEngine.ChunkType.Same, "a");
            AssertCorrectChunk(blocks[0].Chunks[1], DiffEngine.ChunkType.Right, "b");
            AssertCorrectChunk(blocks[0].Chunks[2], DiffEngine.ChunkType.Same, "1", "2", "3");

            AssertBlockMetadata(blocks[1], 6, 4, 7, 5);
            Assert.Equal(3, blocks[1].Chunks.Count);
            AssertCorrectChunk(blocks[1].Chunks[0], DiffEngine.ChunkType.Same, "5", "6", "7");
            AssertCorrectChunk(blocks[1].Chunks[1], DiffEngine.ChunkType.Right, "c");
            AssertCorrectChunk(blocks[1].Chunks[2], DiffEngine.ChunkType.Same, "d");
        }
    }
}